import type {
  AutomationStep,
  BookingContext,
  BookingResult,
  IAutomationEngine,
  StepResult,
  TimeslotInfo,
} from '@shared/types/'
import { getConfig } from '../config/index'
import {
  createTrackedContextLogger,
  generateTraceId,
  log,
  PerformanceLogger,
} from '../logger/index'
import { UserLogger } from '../logger/user-logger'
import { getPageParser } from '../shared/page-parser'
import { getBrowserManager } from './index'

export class BookingEngine implements IAutomationEngine {
  private currentStep = ''
  private progress = 0
  private cancelled = false
  private config = getConfig()
  private parser = getPageParser()
  private traceId: string = ''
  private sessionId: string = ''
  private contextLogger: ReturnType<typeof createTrackedContextLogger> | null = null
  /** Job ID（用于UI日志记录） */
  private jobId: string | undefined

  async execute(context: BookingContext): Promise<BookingResult> {
    this.cancelled = false
    this.progress = 0
    this.traceId = generateTraceId()
    this.sessionId = `booking-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`
    this.jobId = context.options?.jobId // 从 options 获取 jobId

    // 初始化上下文日志记录器
    this.contextLogger = createTrackedContextLogger({
      component: 'BookingEngine',
      sessionId: this.sessionId,
      userName: context.user_info.license_owner_name,
      passport: context.user_info.passport_number,
    }, this.traceId)

    // 记录预约流程开始（文件日志）
    log.booking.start(this.sessionId, context.user_info, { traceId: this.traceId })

    // 记录用户日志（UI展示）
    if (this.jobId) {
      await UserLogger.step(this.jobId, 'startBooking', 5)
    }

    PerformanceLogger.start(`booking-${this.sessionId}`)

    const browser = getBrowserManager()

    try {
      await this.updateProgress(5, '启动浏览器', context)
      this.contextLogger.info('🚀 启动浏览器', {
        headless: context.options?.headless ?? true,
        timeout: context.options?.timeout ?? 30000,
      })

      await browser.launch({
        headless: context.options?.headless ?? true,
        timeout: context.options?.timeout ?? 30000,
      })

      const page = browser.getCurrentPage()
      if (!page)
        throw new Error('页面未初始化')

      const steps = this.createBookingSteps()
      for (const step of steps) {
        if (this.cancelled)
          throw new Error('预约流程已被取消')

        // 记录步骤开始
        log.booking.step(this.sessionId, step.name, 'start', undefined, { traceId: this.traceId })
        PerformanceLogger.start(`step-${step.name}`)

        const result = await this.executeStep(step, context)
        const stepDuration = PerformanceLogger.end(`step-${step.name}`)

        if (!result.success) {
          // 文件日志
          log.booking.step(this.sessionId, step.name, 'error', stepDuration, {
            traceId: this.traceId,
            error: result.error,
          })

          // 用户日志（UI展示）
          if (this.jobId) {
            const error = result.error instanceof Error ? result.error : new Error(String(result.error))
            await UserLogger.error(this.jobId, error, step.name)
          }

          throw new Error(`步骤失败: ${step.name} - ${result.error}`)
        }

        log.booking.step(this.sessionId, step.name, 'success', stepDuration, { traceId: this.traceId })

        if (this.config.logging.save_html && result.html_saved) {
          this.contextLogger!.debug(`HTML 已保存: ${result.html_saved}`)
        }
      }

      const totalDuration = PerformanceLogger.end(`booking-${this.sessionId}`)
      const bookingResult = {
        success: true,
        reference_number: (context.session_data as any)?.reference_number,
        message: '预约成功完成',
      }

      // 记录预约流程成功
      log.booking.end(this.sessionId, true, bookingResult, totalDuration, { traceId: this.traceId })

      // 记录用户日志（UI展示）
      if (this.jobId) {
        await UserLogger.step(this.jobId, 'complete', 100, '预约完成')
      }

      return bookingResult
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      const totalDuration = PerformanceLogger.measure(`booking-${this.sessionId}`) || 0

      // 记录预约流程失败
      const failureResult = { success: false, error_code: 'BOOKING_FAILED', message }
      log.booking.end(this.sessionId, false, failureResult, totalDuration, { traceId: this.traceId })

      // 记录用户日志（UI展示）
      if (this.jobId && e instanceof Error) {
        await UserLogger.error(this.jobId, e, '预约流程')
      }

      // 记录系统错误
      if (e instanceof Error) {
        log.system.error(e, 'BookingEngine.execute', {
          traceId: this.traceId,
          sessionId: this.sessionId,
          user_action: '用户发起预约流程',
          recovery_suggestion: '请检查网络连接和用户信息，稍后重试',
        })
      }
      else {
        this.contextLogger?.error('预约流程执行失败', { error: message })
      }

      return failureResult
    }
    finally {
      try {
        await browser.close()
        this.contextLogger?.info('浏览器已关闭')
      }
      catch (err) {
        this.contextLogger?.warn('关闭浏览器时出现问题', { error: String(err) })
      }
    }
  }

  async executeStep(step: AutomationStep, context: BookingContext): Promise<StepResult> {
    const startTime = Date.now()
    try {
      const timeout = step.timeout || context.options?.timeout || 30000
      const result = await Promise.race([
        step.execute(context),
        new Promise<StepResult>((_, reject) => setTimeout(() => reject(new Error('步骤执行超时')), timeout)),
      ])
      const duration = Date.now() - startTime
      this.contextLogger?.debug(`步骤执行完成`, {
        step: step.name,
        duration,
        timeout,
        durationText: `${duration}ms`,
        timeoutText: `${timeout}ms`,
      })
      return result
    }
    catch (e: unknown) {
      this.contextLogger?.error(`步骤执行失败`, { step: step.name, error: String(e) })

      if (step.retry && step.retry.max_attempts > 1) {
        this.contextLogger?.info(`开始重试步骤`, {
          step: step.name,
          remainingAttempts: step.retry.max_attempts - 1,
        })

        for (let attempt = 1; attempt < step.retry.max_attempts; attempt++) {
          await new Promise(r => setTimeout(r, step.retry!.delay_ms))
          try {
            PerformanceLogger.start(`retry-${step.name}-${attempt}`)
            const retryResult = await step.execute(context)
            const retryDuration = PerformanceLogger.end(`retry-${step.name}-${attempt}`)

            this.contextLogger?.info(`步骤重试成功`, {
              step: step.name,
              attempt,
              duration: retryDuration,
              durationText: `${retryDuration}ms`,
            })
            return retryResult
          }
          catch (err) {
            this.contextLogger?.warn(`步骤重试失败`, {
              step: step.name,
              attempt,
              error: String(err),
            })
          }
        }
      }

      const message = e instanceof Error ? e.message : String(e)
      return { success: false, error: message }
    }
  }

  getProgress(): number { return this.progress }
  getCurrentStep(): string { return this.currentStep }
  async cancel(): Promise<void> {
    this.cancelled = true
    this.contextLogger?.info('预约流程已请求取消', { sessionId: this.sessionId })
  }

  private async updateProgress(progress: number, step: string, context?: BookingContext): Promise<void> {
    this.progress = progress
    this.currentStep = step

    // 如果有 jobId，使用 UserLogger 记录用户友好日志
    if (this.jobId) {
      await UserLogger.step(this.jobId, step, progress)
    }

    // 调用原有的 onProgress 回调（如果存在）
    if (context?.options?.onProgress) {
      context.options.onProgress({
        progress,
        step,
        timestamp: Math.floor(Date.now() / 1000),
      })
    }
  }

  private createBookingSteps(): AutomationStep[] {
    return [
      { name: '访问首页', description: '打开预约系统首页', execute: async (c) => {
        await this.updateProgress(10, '访问首页', c)
        return await this.stepNavigateHome(c)
      }, retry: { max_attempts: 3, delay_ms: 2000 } },
      { name: '同意条款', description: '同意服务条款并继续', execute: async (c) => {
        await this.updateProgress(20, '同意条款', c)
        return await this.stepAgreeTerms(c)
      }, retry: { max_attempts: 2, delay_ms: 1000 } },
      { name: '填写个人信息', description: '填写用户个人信息表单', execute: async (c) => {
        await this.updateProgress(35, '填写个人信息', c)
        return await this.stepFillPersonalInfo(c)
      }, retry: { max_attempts: 2, delay_ms: 1500 } },
      { name: '身份验证', description: '提交身份验证信息', execute: async (c) => {
        await this.updateProgress(50, '身份验证', c)
        return await this.stepVerifyIdentity(c)
      }, retry: { max_attempts: 3, delay_ms: 2000 } },
      { name: '查询可用日期', description: '获取可预约的日期列表', execute: async (c) => {
        await this.updateProgress(65, '查询可用日期', c)
        return await this.stepQueryAvailableDates(c)
      }, retry: { max_attempts: 5, delay_ms: 3000 } },
      { name: '选择时段', description: '选择合适的预约时段', execute: async (c) => {
        await this.updateProgress(80, '选择时段', c)
        return await this.stepSelectTimeslot(c)
      }, retry: { max_attempts: 3, delay_ms: 1500 } },
      { name: '确认预约', description: '确认预约信息并提交', execute: async (c) => {
        await this.updateProgress(95, '确认预约', c)
        return await this.stepConfirmBooking(c)
      }, retry: { max_attempts: 2, delay_ms: 2000 } },
    ]
  }

  // 步骤实现
  private async stepNavigateHome(_context: BookingContext): Promise<StepResult> {
    const browser = getBrowserManager()
    const page = browser.getCurrentPage()!
    this.contextLogger?.info('开始访问首页', { url: this.config.network.base_url })

    try {
      await page.goto(this.config.network.base_url, { waitUntil: 'load' })
      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)

      const homeUrl = `${this.config.network.base_url}?cmd=cmdShowHome`
      this.contextLogger?.debug('导航到首页', { url: homeUrl })
      await page.goto(homeUrl, { waitUntil: 'load' })
      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)

      const html = await page.content()
      const pageType = this.parser.identifyPageType(html, page.url())
      this.contextLogger?.debug('页面类型识别完成', { pageType, currentUrl: page.url() })

      const errorResult = this.parser.detectError(html)
      if (errorResult.success && errorResult.data) {
        this.contextLogger?.warn('检测到页面错误', {
          errorCode: errorResult.data.code,
          errorMessage: errorResult.data.message,
        })
        return { success: false, error: `页面错误: ${errorResult.data.message} (${errorResult.data.code})` }
      }

      let htmlPath: string | undefined
      if (this.config.logging.save_html) {
        htmlPath = await browser.saveHtml('01_home', html)
        this.contextLogger?.debug('HTML快照已保存', { path: htmlPath })
      }

      const hasStartButton = html.includes('開始預約') || html.includes('开始预约')
      if (!hasStartButton) {
        this.contextLogger?.warn('首页缺少开始预约按钮')
      }

      this.contextLogger?.info('首页访问成功', { pageType, hasStartButton })
      return { success: true, page_type: pageType, html_saved: htmlPath }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      if (e instanceof Error) {
        log.system.error(e, 'BookingEngine.stepNavigateHome', {
          traceId: this.traceId,
          sessionId: this.sessionId,
          url: this.config.network.base_url,
          user_action: '用户尝试访问预约首页',
          recovery_suggestion: '检查网络连接和URL配置',
        })
      }
      else {
        this.contextLogger?.error('访问首页失败', { error: message, url: this.config.network.base_url })
      }
      return { success: false, error: `访问首页失败: ${message}` }
    }
  }

  private async stepAgreeTerms(_context: BookingContext): Promise<StepResult> {
    const browser = getBrowserManager()
    const page = browser.getCurrentPage()!
    this.contextLogger?.info('开始处理条款页面')
    try {
      // Form submission is performed via implicit navigation; explicit body not required.

      const startUrl = `${this.config.network.base_url}?cmd=cmdStartNewApplication`
      this.contextLogger?.debug('提交开始新申请请求', { url: startUrl })
      const response = await page.goto(startUrl, { waitUntil: 'load' })

      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)
      const html = await page.content()
      const pageType = this.parser.identifyPageType(html, page.url())
      this.contextLogger?.debug('条款页面加载完成', {
        pageType,
        status: response?.status(),
        currentUrl: page.url(),
      })

      const errorResult = this.parser.detectError(html)
      if (errorResult.success && errorResult.data) {
        this.contextLogger?.warn('检测到条款页面错误', {
          errorCode: errorResult.data.code,
          errorMessage: errorResult.data.message,
        })
        return { success: false, error: `条款页面错误: ${errorResult.data.message}` }
      }

      let htmlPath: string | undefined
      if (this.config.logging.save_html) {
        htmlPath = await browser.saveHtml('02_terms', html)
        this.contextLogger?.debug('条款页面HTML快照已保存', { path: htmlPath })
      }

      this.contextLogger?.info('条款页面处理成功', { pageType })
      return { success: true, page_type: pageType, html_saved: htmlPath }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      if (e instanceof Error) {
        log.system.error(e, 'BookingEngine.stepAgreeTerms', {
          traceId: this.traceId,
          sessionId: this.sessionId,
          user_action: '用户尝试同意服务条款',
          recovery_suggestion: '检查网络连接，稍后重试',
        })
      }
      else {
        this.contextLogger?.error('条款页面处理失败', { error: message })
      }
      return { success: false, error: `同意条款失败: ${message}` }
    }
  }

  private async stepFillPersonalInfo(context: BookingContext): Promise<StepResult> {
    const browser = getBrowserManager()
    const page = browser.getCurrentPage()!
    this.contextLogger?.info('开始填写个人信息', {
      passport: context.user_info.passport_number,
      license: context.user_info.license_number,
      name: context.user_info.license_owner_name,
    })
    try {
      const infoUrl = `${this.config.network.base_url}?cmd=cmdInputPersonalInfo`

      await page.goto(infoUrl, { waitUntil: 'load' })

      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)
      const html = await page.content()
      const pageType = this.parser.identifyPageType(html, page.url())
      this.contextLogger?.debug('个人信息页面加载完成', { pageType, currentUrl: page.url() })

      const errorResult = this.parser.detectError(html)
      if (errorResult.success && errorResult.data) {
        this.contextLogger?.warn('检测到个人信息页面错误', errorResult.data)
        return { success: false, error: `个人信息页面错误: ${errorResult.data.message}` }
      }

      this.contextLogger?.debug('点击 DI 服务开始预约按钮')
      try {
        await page.click('input[onclick="javascript:onClickDISevType(\'DI\')"]')
        await this.randomDelay({ min: 1, max: 2 })
      }
      catch (e) {
        this.contextLogger?.warn('点击 DI 服务按钮失败，尝试继续', { error: e instanceof Error ? e.message : String(e) })
      }

      const userInfo = context.user_info
      const fillOperations = [
        { id: 'diPassportNumber', value: userInfo.passport_number, name: '护照号码' },
        { id: 'txtLicenseNumber', value: userInfo.license_number, name: '驾照号码' },
        { id: 'txtLicenseOwnerName', value: userInfo.license_owner_name, name: '姓名' },
        { id: 'txtEmailAddress', value: userInfo.email, name: '邮箱' },
      ] as Array<{ id: string, value: string, name: string }>
      if (userInfo.phone_number)
        fillOperations.push({ id: 'txtPhoneNumber', value: userInfo.phone_number, name: '电话' })

      for (const op of fillOperations) {
        try {
          const element = await page.$(`#${op.id}`)
          if (element) {
            await element.fill(op.value)
            this.contextLogger?.debug(`已填写 ${op.name}`, { value: op.value })
            await this.randomDelay({ min: 0.3, max: 0.8 })
          }
          else {
            this.contextLogger?.warn(`未找到字段 ${op.name}`, { id: op.id })
          }
        }
        catch (e) {
          this.contextLogger?.warn(`填写 ${op.name} 失败`, { error: e instanceof Error ? e.message : String(e) })
        }
      }

      try {
        await page.selectOption('select[name="issuingCountry"]', userInfo.issuing_country)
        this.contextLogger?.debug('已选择签发国家', { country: userInfo.issuing_country })
      }
      catch (e) { this.contextLogger?.warn('设置签发国家失败', { error: e instanceof Error ? e.message : String(e) }) }
      try {
        await page.selectOption('select[name="issuingAuthority"]', userInfo.issuing_authority)
        this.contextLogger?.debug('已选择签发机关', { authority: userInfo.issuing_authority })
      }
      catch (e) { this.contextLogger?.warn('设置签发机关失败', { error: e instanceof Error ? e.message : String(e) }) }

      let htmlPath: string | undefined
      if (this.config.logging.save_html) {
        const currentHtml = await page.content()
        htmlPath = await browser.saveHtml('03_personal_info_filled', currentHtml)
        this.contextLogger?.debug('个人信息页面 HTML 快照已保存', { path: htmlPath })
      }

      this.contextLogger?.info('个人信息填写完成', { pageType })
      return { success: true, page_type: pageType, html_saved: htmlPath }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      this.contextLogger?.error('填写个人信息失败', { error: message })
      return { success: false, error: `填写个人信息失败: ${message}` }
    }
  }

  private async stepVerifyIdentity(context: BookingContext): Promise<StepResult> {
    const browser = getBrowserManager()
    const page = browser.getCurrentPage()!
    this.contextLogger?.info('开始身份验证阶段')
    try {
      const currentHtml = await page.content()
      const formFields = this.parser.extractFormFields(currentHtml, 'frmAppointment')
      if (!formFields.success)
        this.contextLogger?.warn('未能提取表单数据，尝试继续')

      const userInfo = context.user_info
      // Identity verification uses implicit submission; keep formFields for completeness.

      const verifyUrl = `${this.config.network.base_url}?cmd=cmdCheckIdentifierAction`
      this.contextLogger?.debug('提交身份验证请求', { url: verifyUrl, passport: userInfo.passport_number })
      const response = await page.goto(verifyUrl, { waitUntil: 'load' })

      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)
      const html = await page.content()
      const pageType = this.parser.identifyPageType(html, page.url())
      this.contextLogger?.debug('身份验证响应处理完成', { pageType, status: response?.status(), currentUrl: page.url() })

      const errorResult = this.parser.detectError(html)
      if (errorResult.success && errorResult.data) {
        this.contextLogger?.error('身份验证出现错误', errorResult.data)
        return { success: false, error: `身份验证失败: ${errorResult.data.message} (${errorResult.data.code})` }
      }

      const needsPhoneNumber = html.includes('請輸入電話號碼') || html.includes('电话号码') || html.includes('Phone Number')
      if (needsPhoneNumber && !userInfo.phone_number) {
        this.contextLogger?.warn('需要填写电话号码，但用户信息中未提供')
        return { success: false, error: '系统要求填写电话号码，请在用户信息中添加电话号码' }
      }

      let htmlPath: string | undefined
      if (this.config.logging.save_html) {
        htmlPath = await browser.saveHtml('04_identity_check', html)
        this.contextLogger?.debug('身份验证 HTML 快照已保存', { path: htmlPath })
      }

      this.contextLogger?.info('身份验证成功', { pageType, needsPhoneNumber })
      return { success: true, page_type: pageType, html_saved: htmlPath }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      this.contextLogger?.error('身份验证失败', { error: message })
      return { success: false, error: `身份验证失败: ${message}` }
    }
  }

  private async stepQueryAvailableDates(context: BookingContext): Promise<StepResult> {
    const browser = getBrowserManager()
    const page = browser.getCurrentPage()!
    this.contextLogger?.info('开始查询可用日期')
    try {
      const currentHtml = await page.content()
      const formFields = this.parser.extractFormFields(currentHtml, 'frmAppointment')
      if (!formFields.success)
        this.contextLogger?.warn('未能提取表单数据，使用默认值')

      const now = new Date()
      const formData: Record<string, string> = {
        ...(formFields.data || {}),
        storedAppointmentOfficeCode: this.config.booking.default_office_code,
        storedAppointmentCalendarYear: now.getFullYear().toString(),
        storedAppointmentCalendarMonth: now.getMonth().toString(),
        storedAppointmentOperation: 'A',
        timestamp: Date.now().toString(),
      }

      const calendarUrl = `${this.config.network.base_url}?cmd=cmdGetCalendarAction`
      this.contextLogger?.debug('提交日历查询请求', { url: calendarUrl, office: formData.storedAppointmentOfficeCode, year: formData.storedAppointmentCalendarYear, month: formData.storedAppointmentCalendarMonth })
      const response = await page.goto(calendarUrl, { waitUntil: 'load' })

      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)
      const calendarHtml = await page.content()
      const pageType = this.parser.identifyPageType(calendarHtml, page.url())
      this.contextLogger?.debug('日历页面加载完成', { pageType, status: response?.status(), currentUrl: page.url() })

      const errorResult = this.parser.detectError(calendarHtml)
      if (errorResult.success && errorResult.data) {
        this.contextLogger?.error('日历页面出现错误', errorResult.data)
        return { success: false, error: `日历页面错误: ${errorResult.data.message} (${errorResult.data.code})` }
      }

      if (pageType !== 'calendar')
        this.contextLogger?.warn('页面类型不匹配', { expected: 'calendar', actual: pageType })

      const datesResult = this.parser.parseAvailableDates(calendarHtml)
      if (!datesResult.success) {
        this.contextLogger?.error('解析可用日期失败', { error: datesResult.error })
        return { success: false, error: `解析日期失败: ${datesResult.error}` }
      }

      if (!datesResult.data || datesResult.data.length === 0) {
        this.contextLogger?.warn('当前月份没有可用日期，尝试下个月')
        return { success: false, error: '当前没有可用的预约日期' }
      }

      // 会话范围过滤（仅在未命中首选时生效）：由 UI 通过 context.options.allowed_date_range 传入
      const allowed = (context.options as any)?.allowed_date_range as { start?: string, end?: string } | undefined
      const parseAllowed = (s?: string): Date | undefined => {
        if (!s || !String(s).trim())
          return undefined
        const v = String(s).trim()
        if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
          const [y, m, d] = v.split('-').map(Number)
          const dt = new Date(y, m - 1, d); dt.setHours(0, 0, 0, 0)
          return dt
        }
        // 本引擎解析的日期字符串为 DD-MM-YYYY
        try {
          const [dd, mm, yyyy] = v.split('-').map(Number)
          const dt = new Date(yyyy, (mm || 1) - 1, dd)
          dt.setHours(0, 0, 0, 0)
          return dt
        }
        catch { return undefined }
      }
      const allowedStart = parseAllowed(allowed?.start)
      const allowedEnd = parseAllowed(allowed?.end)

      let selectedDate = datesResult.data[0]
      // 首选日期优先（不受 allowed 限制，只要可用即可）—兼容 YYYY-MM-DD 与 DD-MM-YYYY
      const toDMY = (v: string) => {
        const s = String(v || '').trim()
        if (/^\d{2}-\d{2}-\d{4}$/.test(s))
          return s
        const m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/)
        if (m)
          return `${m[3]}-${m[2]}-${m[1]}`
        return s
      }
      if (context.preferred_dates && context.preferred_dates.length > 0) {
        const preferredDMY = context.preferred_dates.map(toDMY)
        this.contextLogger?.debug('尝试匹配首选日期', { preferred: preferredDMY, available: datesResult.data })
        for (const preferredDate of preferredDMY) {
          if (datesResult.data.includes(preferredDate)) {
            selectedDate = preferredDate
            this.contextLogger?.info('找到匹配的首选日期', { selectedDate })
            break
          }
        }
      }

      if (!context.preferred_dates || !context.preferred_dates.map(toDMY).includes(selectedDate)) {
        // 未命中首选时，根据 allowed 过滤候选
        const toDate = (dmy: string) => {
          const [dd, mm, yyyy] = dmy.split('-')
          const dt = new Date(Number(yyyy), Number(mm) - 1, Number(dd))
          dt.setHours(0, 0, 0, 0)
          return dt
        }
        const inAllowed = (dmy: string) => {
          const dt = toDate(dmy)
          if (allowedStart && dt < allowedStart)
            return false
          if (allowedEnd && dt > allowedEnd)
            return false
          return true
        }
        const candidates = datesResult.data.filter(inAllowed)
        if (candidates.length > 0)
          selectedDate = candidates[0]
      }

      context.selected_date = selectedDate
      let htmlPath: string | undefined
      if (this.config.logging.save_html) {
        htmlPath = await browser.saveHtml('05_calendar', calendarHtml)
        this.contextLogger?.debug('日历页面 HTML 快照已保存', { path: htmlPath })
      }

      this.contextLogger?.info('日期查询成功', { availableDates: datesResult.data, selectedDate, totalCount: datesResult.data.length })
      return { success: true, page_type: pageType, data: { available_dates: datesResult.data, selected_date: selectedDate }, html_saved: htmlPath }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      this.contextLogger?.error('查询可用日期失败', { error: message })
      return { success: false, error: `查询可用日期失败: ${message}` }
    }
  }

  private async stepSelectTimeslot(context: BookingContext): Promise<StepResult> {
    const browser = getBrowserManager()
    const page = browser.getCurrentPage()!
    if (!context.selected_date)
      return { success: false, error: '未选择预约日期' }
    this.contextLogger?.info('开始查询可用时段', { selectedDate: context.selected_date })
    try {
      const currentHtml = await page.content()
      const formFields = this.parser.extractFormFields(currentHtml, 'frmAppointment')
      if (!formFields.success)
        this.contextLogger?.warn('未能提取表单数据，使用基本参数')

      // Timeslot query uses implicit submission; explicit body is not required.

      const timeslotUrl = `${this.config.network.base_url}?cmd=cmdGetTimeslotListAction`
      this.contextLogger?.debug('提交时段查询请求', { url: timeslotUrl, date: context.selected_date })
      const response = await page.goto(timeslotUrl, { waitUntil: 'load' })

      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)
      const timeslotHtml = await page.content()
      const pageType = this.parser.identifyPageType(timeslotHtml, page.url())
      this.contextLogger?.debug('时段页面加载完成', { pageType, status: response?.status(), currentUrl: page.url() })

      const errorResult = this.parser.detectError(timeslotHtml)
      if (errorResult.success && errorResult.data) {
        this.contextLogger?.error('时段页面出现错误', errorResult.data)
        return { success: false, error: `时段页面错误: ${errorResult.data.message} (${errorResult.data.code})` }
      }

      if (pageType !== 'timeslot')
        this.contextLogger?.warn('页面类型不匹配', { expected: 'timeslot', actual: pageType })

      const timeslotsResult = this.parser.parseAvailableTimeslots(timeslotHtml)
      if (!timeslotsResult.success) {
        this.contextLogger?.error('解析可用时段失败', { error: timeslotsResult.error })
        return { success: false, error: `解析时段失败: ${timeslotsResult.error}` }
      }

      if (!timeslotsResult.data || timeslotsResult.data.length === 0) {
        this.contextLogger?.warn('该日期没有可用时段', { date: context.selected_date })
        return { success: false, error: `${context.selected_date} 该日期没有可用时段` }
      }

      let selectedTimeslot: TimeslotInfo = timeslotsResult.data[0]
      if (context.preferred_times && context.preferred_times.length > 0) {
        this.contextLogger?.debug('尝试匹配首选时段', { preferred: context.preferred_times, available: timeslotsResult.data.map(t => t.display_time) })
        for (const preferredTime of context.preferred_times) {
          const matchingSlot = timeslotsResult.data.find(slot => slot.display_time.includes(preferredTime) || slot.start_time === preferredTime.replace(':', ''))
          if (matchingSlot) {
            selectedTimeslot = matchingSlot
            this.contextLogger?.info('找到匹配的首选时段', { selectedTimeslot })
            break
          }
        }
      }

      context.selected_timeslot = {
        ...selectedTimeslot,
        date: context.selected_date,
      }
      let htmlPath: string | undefined
      if (this.config.logging.save_html) {
        htmlPath = await browser.saveHtml('06_timeslot', timeslotHtml)
        this.contextLogger?.debug('时段页面 HTML 快照已保存', { path: htmlPath })
      }

      this.contextLogger?.info('时段选择成功', {
        availableTimeslots: timeslotsResult.data.map(t => ({ id: t.id, time: t.display_time })),
        selectedTimeslot: { id: selectedTimeslot.id, time: selectedTimeslot.display_time },
        totalCount: timeslotsResult.data.length,
      })

      return { success: true, page_type: pageType, data: { available_timeslots: timeslotsResult.data, selected_timeslot: selectedTimeslot }, html_saved: htmlPath }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      this.contextLogger?.error('选择时段失败', { error: message })
      return { success: false, error: `选择时段失败: ${message}` }
    }
  }

  private async stepConfirmBooking(context: BookingContext): Promise<StepResult> {
    const browser = getBrowserManager()
    const page = browser.getCurrentPage()!
    if (!context.selected_timeslot)
      return { success: false, error: '未选择预约时段' }
    this.contextLogger?.info('开始确认预约', { timeslot: context.selected_timeslot.display_time, timeslotId: context.selected_timeslot.id, date: context.selected_date })
    try {
      const currentHtml = await page.content()
      const formFields = this.parser.extractFormFields(currentHtml, 'frmAppointment')
      if (!formFields.success)
        this.contextLogger?.warn('未能提取表单数据，使用基本参数')

      const confirmUrl = `${this.config.network.base_url}?cmd=cmdConfirmAppointment`
      this.contextLogger?.debug('提交确认页面请求', { url: confirmUrl })
      const confirmResponse = await page.goto(confirmUrl, { waitUntil: 'load' })

      await this.randomDelay(this.config.booking.human_like_delays.page_view_delay)
      const confirmHtml = await page.content()
      const confirmPageType = this.parser.identifyPageType(confirmHtml, page.url())
      this.contextLogger?.debug('确认页面加载完成', { pageType: confirmPageType, status: confirmResponse?.status(), currentUrl: page.url() })

      const confirmErrorResult = this.parser.detectError(confirmHtml)
      if (confirmErrorResult.success && confirmErrorResult.data) {
        this.contextLogger?.error('确认页面出现错误', confirmErrorResult.data)
        return { success: false, error: `确认页面错误: ${confirmErrorResult.data.message} (${confirmErrorResult.data.code})` }
      }

      if (this.config.logging.save_html)
        await browser.saveHtml('07_confirm', confirmHtml)
      await this.randomDelay(this.config.booking.human_like_delays.pre_submit_delay)

      // 最终预约提交采用隐式跳转，无需显式提交表单内容。
      const submitUrl = `${this.config.network.base_url}?cmd=cmdSubmitAppointmentAction`
      this.contextLogger?.debug('提交最终预约请求', { url: submitUrl })
      this.contextLogger?.info('提交最终预约请求...')
      const submitResponse = await page.goto(submitUrl, { waitUntil: 'load' })

      await page.waitForLoadState('load')
      const resultHtml = await page.content()
      const resultType = this.parser.identifyPageType(resultHtml, page.url())
      this.contextLogger?.debug('预约结果页面加载完成', { pageType: resultType, status: submitResponse?.status(), currentUrl: page.url() })

      const bookingResult = this.parser.parseBookingResult(resultHtml)
      let htmlPath: string | undefined
      if (this.config.logging.save_html) {
        htmlPath = await browser.saveHtml('08_final_result', resultHtml)
        this.contextLogger?.debug('最终结果页面 HTML 快照已保存', { path: htmlPath })
      }

      if (bookingResult.success && bookingResult.data?.success) {
        if (bookingResult.data.reference_number) {
          if (!context.session_data)
            (context as any).session_data = {};
          (context.session_data as any).reference_number = bookingResult.data.reference_number
          this.contextLogger?.info('预约成功', {
            referenceNumber: bookingResult.data.reference_number,
            date: context.selected_date,
            time: context.selected_timeslot?.display_time,
            userName: context.user_info.license_owner_name,
          })
        }
        else {
          this.contextLogger?.info('预约提交成功，但未找到参考编号')
        }
        return { success: true, page_type: resultType, data: bookingResult.data, html_saved: htmlPath }
      }
      else {
        const errorMessage = bookingResult.data?.message || '预约提交失败'
        this.contextLogger?.error('预约失败', { error: errorMessage, errorCode: bookingResult.data?.error_code })
        return { success: false, error: errorMessage, html_saved: htmlPath }
      }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      this.contextLogger?.error('确认预约过程失败', { error: message })
      return { success: false, error: `确认预约失败: ${message}` }
    }
  }

  private async randomDelay(range: { min: number, max: number }): Promise<void> {
    const delay = (range.min + Math.random() * (range.max - range.min)) * 1000
    await new Promise(resolve => setTimeout(resolve, delay))
  }
}

let bookingEngine: BookingEngine | null = null
export function getBookingEngine(): BookingEngine {
  if (!bookingEngine)
    bookingEngine = new BookingEngine()
  return bookingEngine
}
